Blog

Was ist neu in Angular 17?

Die neuesten Features unter der Lupe

Nov 16, 2023

Anfang 2023 hat Sarah Drashner, die als Director of Engineering bei Google unter anderem auch dem Angular-Team vorsteht, den Begriff Angular Renaissance geprägt. Gemeint ist damit eine Erneuerung des Frameworks, das uns nun schon sieben Jahre lang bei der Entwicklung moderner JavaScript-Lösungen begleitet.

Diese Erneuerung erfolgt inkrementell sowie abwärtskompatibel und berücksichtigt aktuelle Trends aus der Welt der Frontend-Frameworks. Dabei geht es in erster Linie um Developer Experience und Performance. Standalone Components und Signals sind zwei bekannte Features, die bereits im Rahmen dieser Bewegung entstanden sind.

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

Angular 17 wird im Herbst 2023 weitere Beiträge zur Angular Renaissance leisten: Es kommt mit einer neuen Syntax für den Control Flow, verzögertes Laden von Seitenteilen und einer besseren Unterstützung für SSR. Außerdem setzt das CLI nun auf esbuild und beschleunigt damit den Build erheblich. In diesem Artikel gehe ich anhand einer Beispielanwendung (Abb. 1) auf diese Neuerungen ein. Den verwendeten Quellcode findet man unter [1].

 

Abb. 1: Beispielanwendung

Neue Syntax für Control Flow in Templates

Seit seinen ersten Tagen hat Angular strukturelle Direktiven wie *ngIf oder *ngFor für den Control Flow verwendet. Da es den Control Flow nun für die mit Angular 16 eingeführten Signals ohnehin umfangreich zu überarbeiten gilt, hat sich das Angular-Team entschieden, ihn gleich auf neue Beine zu stellen. Das Ergebnis ist der neue Build-in Control Flow, der sich deutlich vom gerenderten Markup abhebt (Listing 1).

 

Listing 1

@for (product of products(); track product.id) {
  <div class="card">
    <h2 class="card-title">{{product.productName}}</h2>
    […]
  </div>
}
@empty {
  <p class="text-lg">No Products found!</p>
}

 

 

Beachtenswert ist hier unter anderem der neue @empty-Block, den Angular rendert, wenn die zu iterierende Auflistung leer ist.

Auch wenn Signals ein Treiber für diese neue Syntax waren, sind sie keine Voraussetzung für ihre Nutzung. Die neuen Control-Flow-Blöcke lassen sich genauso mit klassischen Variablen oder auch mit Observables im Zusammenspiel mit der async Pipe nutzen.

Der verpflichtende track-Ausdruck erlaubt es Angular, einzelne Elemente, die innerhalb der iterierten Auflistung verschoben wurden, zu identifizieren. Damit lässt sich der Aufwand für das Rendering drastisch reduzieren und bestehende DOM-Knoten wiederverwenden. Beim Iterieren von Auflistungen primitiver Typen, z. B. number oder string, sollte track Angaben des Angular-Teams zur Folge mit der Pseudovariable $index verwendet werden (Listing 2).

 

 

Listing 2

@for (group of groups(); track $index) {
  <a (click)="groupSelected(group)">{{group}}</a>
  @if (!$last) {
    <span class="mr-5 ml-5">|</span>
  }
}

 

Neben $index stehen auch die anderen von *ngFor bekannten Werte über Pseudovariablen zur Verfügung: $count, $first, $last, $even, $odd. Bei Bedarf lassen sich deren Werte über Ausdrücke in Templatevariablen ablegen (Listing 3).

 

Listing 3

@for (group of groups(); track $index; let isLast = $last) {
  <a (click)="groupSelected(group)">{{group}}</a>
  @if (!isLast) {
    <span class="mr-5 ml-5">|</span>
  }
}

 

Das neue @if vereinfacht das Formulieren von else-/else-if-Zweigen (Listing 4).

 

Listing 4

@if (product().discountedPrice && product().discountMinCount) {
  [...]
}
@else if (product().discountedPrice && !product().discountMinCount) {
  [...]
}
@else {
  [...]
}

 

Daneben lassen sich verschiedene Fälle auch mit einem @switch unterscheiden (Listing 5).

 

Listing 5

@switch (mode) {
  @case ('full') {
    [...]
  }
  @case ('small') {
    [...]
  }
  @default {
    [...]
  }
}

 

Im Gegensatz zu ngSwitch und *ngSwitchCase ist die neue Syntax typsicher. Im betrachteten Beispiel müssen die einzelnen @case-Blöcke Stringwerte aufweisen, zumal die an @switch übergebene Variable mode auch vom Typ string ist.

Die neue Control-Flow-Syntax verringert die Notwendigkeit für den Einsatz struktureller Direktiven, die zwar mächtig, aber teilweise auch unnötig komplex sind. Trotzdem wird das Framework strukturelle Direktiven weiterhin unterstützen. Zum einen gibt es einige valide Anwendungsfälle dafür und zum anderen gilt es trotz der vielen aufregenden Neuerungen das Framework abwärtskompatibel zu gestalten.

 

Automatische Migration zum Build-in Control Flow

Wer seinen Programmcode automatisiert auf die neue Control-Flow-Syntax migrieren möchte, findet nun im Paket @angular/core ein Schematic dafür:

 

ng g @angular/core:control-flow

 

Verzögertes Laden von Seitenteilen

Typischerweise sind nicht alle Bereiche einer Seite gleich wichtig. Bei der Detailansicht eines Produkts geht es zunächst um das Produkt selbst. Vorschläge für ähnliche Produkte sind zweitrangig. Das ändert sich jedoch schlagartig, sobald Benutzer:innen die Produktvorschläge in den sichtbaren Bereich des Browserfensters, den sogenannten View Port, scrollen.

Bei besonders performancekritischen Webanwendungen wie Webshops bietet es sich an, solche zunächst weniger wichtigen Seitenteile verzögert zu laden. Das führt dazu, dass die wirklich wichtigen Elemente rascher verfügbar sind. Wer diese Idee bisher in Angular umsetzen wollte, musste sich manuell darum kümmern. Angular 17 vereinfacht auch diese Aufgabe drastisch mit dem neuen @defer-Block (Listing 6).

 

Listing 6

@defer (on viewport) {
  <app-recommentations [productGroup]="product().productGroup"></app-recommentations>
}
@placeholder {
  <app-ghost-products></app-ghost-products>
}

 

Der Einsatz von @defer zögert das Laden der angegebenen Komponente (eigentlich: das Laden des angegebenen Seitenbereichs) hinaus, bis ein bestimmtes Ereignis eintritt. Als Ersatz präsentiert es den unter @placeholder angegebenen Platzhalter. In der hier verwendeten Demoanwendung werden auf diese Weise zunächst Ghost Elements für die Produktvorschläge präsentiert (Abb. 2).

 

Abb. 2: Ghost Elements als Platzhalter

 

Nach dem Laden tauscht @defer die Ghost Elements gegen die tatsächlichen Vorschläge (Abb. 3) aus.

 

Abb. 3: @defer tauscht den Platzhalter gegen die verzögert geladene Komponente

 

Im betrachteten Beispiel kommt das Ereignis on viewport zum Einsatz. Es tritt ein, sobald der Platzhalter in den sichtbaren Bereich des Browserfensters gescrollt wurde. Weitere unterstützte Ereignisse finden sich in Tabelle 1.

 

Trigger Beschreibung
on idle Der Browser meldet, dass gerade keine kritischen Aufgaben anstehen (Standard).
on viewport Der Platzhalter wird in den sichtbaren Bereich der Seite geladen.
on interaction Der Benutzer beginnt, mit dem Platzhalter zu interagieren.
on hover Der Mouse-Cursor wird über dem Platzhalter bewegt.
on immediate So schnell wie möglich nach dem Laden der Seite.
on timer(<duration>) Nach einer bestimmten Zeit, z. B. on timer(5s), um das Laden nach 5 Sekunden anzustoßen.
when <condition> Sobald die angegebene Bedingung erfüllt ist, z. B. when (userName !=== null)

Tabelle 1: Trigger für @defer

 

 

Die Trigger on viewport, on interaction und on hover erzwingen standardmäßig die Angabe eines @placeholder-Blocks. Alternativ dazu können sie sich auch auf andere Seitenteile beziehen, die über eine Templatevariable zu referenzieren sind:

 

<h1 #recommentations>Recommentations</h1>
@defer (on viewport(recommentations)) { <app-recommentations [...] />}

 

Außerdem kann @defer angewiesen werden, das Bundle zu einem früheren Zeitpunkt vorzuladen. Wie beim Preloading von Routen stellt diese Vorgehensweise sicher, dass die Bundles augenblicklich verfügbar sind, sobald man sie benötigt:

 

@defer(on viewport; prefetch on immediate) { [...] }

 

Neben @placeholder bietet @defer auch noch zwei weitere Blöcke: @loading und @error. Ersteren zeigt Angular an, während es das Bundle lädt, letzteren im Fehlerfall. Um ein Flackern zu vermeiden, lassen sich @placeholder sowie @loading mit einer Mindestanzeigendauer konfigurieren. Die Eigenschaft minimum legt den gewünschten Wert fest:

 

@defer ( [...] ) { [...] }
@loading (after 150ms; minimum 150ms) { [...] }
@placeholder (minimum 150ms) { [...] }

 

Die Eigenschaft after gibt zusätzlich an, dass der Loading Indicator nur einzublenden ist, wenn das Laden länger als 150 ms dauert.

Build-Performance mit esbuild

Ursprünglich nutzte das Angular CLI webpack für das Bauen von Bundles. Allerdings ist webpack ein wenig in die Jahre gekommen und wird derzeit von jüngeren Werkzeugen, die einfacher zu nutzen und auch um einiges schneller sind, herausgefordert. Bei esbuild [2] handelt es sich um eines dieser Werkzeuge, das mit über 20 000 Downloads pro Woche auch eine beachtenswerte Verbreitung aufweist.

Bereits seit einigen Releases arbeitet das CLI-Team an einer esbuild-Integration. In Angular 16 war diese Integration bereits im Stadium einer Developer Preview enthalten. Ab Angular 17 ist die Implementierung stabil und kommt über den weiter unten beschriebenen Application Builder für neue Angular-Projekte standardmäßig zum Einsatz.

Bei bestehenden Projekten lohnt es sich, eine Umstellung auf esbuild zu prüfen. Dazu ist in der angular.json der Eintrag builder zu aktualisieren:

 

"builder": "@angular-devkit/build-angular:browser-esbuild"

 

Anders ausgedrückt: Am Ende ist -esbuild zu ergänzen. In den meisten Fällen sollte sich daraufhin ng serve und ng build wie gewohnt verhalten, jedoch um einiges schneller sein. Ersteres nutzt zur Beschleunigung den Vite dev-server [3], um npm-Pakete erst bei Bedarf zu bauen. Daneben hat das CLI-Team noch weitere Performanceoptimierungen vorgesehen.

Der Aufruf von ng build konnte durch den Einsatz von esbuild auch drastisch beschleunigt werden. Faktor 2 bis 4 wird häufig als Bandbreite genannt.

SSR ohne Aufwand mit dem neuen Application Builder

Auch die Unterstützung für Server-side Rendering (SSR) wurde mit Angular 17 drastisch vereinfacht. Beim Generieren eines neuen Projekts mit ng new steht nun ein Schalter –ssr zur Verfügung. Kommt dieser nicht zum Einsatz, fragt die CLI, ob sie SSR einrichten soll (Abb. 4).

 

Abb. 4: ng new richtet auf Wunsch SSR ein

 

Um SSR später zu aktivieren, muss lediglich das Paket @angular/ssr hinzugefügt werden:

 

ng add @angular/ssr

 

Wie der Scope @angular verdeutlicht, stammt dieses Paket direkt vom Angular-Team. Es handelt sich dabei um den Nachfolger des Communityprojekts Angular Universal. Um SSR im Zuge von ng build und ng serve direkt zu berücksichtigen, hat das CLI-Team einen neuen Builder bereitgestellt. Dieser sogenannte Application Builder nutzt die oben erwähnte esbuild-Integration und erzeugt damit Bundles, die sowohl im Browser als auch serverseitig nutzen lassen.

Ein Aufruf von ng serve startet auch einen Development-Server, der sowohl serverseitig rendert als auch die Bundles für den Betrieb im Browser ausliefert. Ein Aufruf von ng build –ssr kümmert sich auch um Bundles für beide Welten sowie um das Bauen eines einfachen Node.js-basierten Servers, dessen Quellcode die oben erwähnten Schematics genieren.

Wer keinen Node.js-Server betreiben kann oder möchte, kann mit ng build –prerender die einzelnen Routen der Anwendung bereits beim Build vorrendern.

Weitere Neuerungen

Neben den bis hier diskutierten Neuerungen bringt Angular 17 noch zahlreiche weitere Abrundungen:

 

  • Der Router unterstützt nun das View Transitions API [4]. Dieses von einigen Browsern angebotene API erlaubt das Animieren von Übergängen, z. B. von einer Route auf eine andere mittels CSS-Animationen. Dieses optionale Feature ist beim Einrichten des Routers über die Funktion withViewTransitions zu aktivieren. Zur Demonstration nutzt das Beispiel unter [1] CSS-Animationen, die vom View Transitions API übernommen wurden.
  • Die in Version 16 als Developer Preview eingeführten Signals sind nun stabil. Eine wichtige Änderung gegenüber Version 16 ist, dass Signals nun standardmäßig auf die Nutzung mit Immutables ausgelegt sind. Somit kann Angular einfacher herausfinden, an welcher Stelle die über Signals verwalteten Datenstrukturen geändert wurden. Zum Aktualisieren von Signals lässt sich die Methode set, die einen neuen Wert zuweist, oder die Methode update, die den bestehenden Wert auf einen neuen abbildet, einsetzen. Die Methode mutate wurde entfernt, zumal sie nicht zur Semantik von Immutables passt.
  • Es existiert nun ein Diagnostic, der eine Warnung ausgibt, wenn beim Lesen von Signals in Templates der Aufruf des Getters vergessen wurde (z. B. {{ products }} anstatt {{ products() }}).
  • Animationen können nun lazy geladen werden [5].
  • Das Angular CLI generiert standardmäßig Standalone Components, Standalone Directives und Standalone Pipes. Auch ng new sieht standardmäßig das Bootstrapping einer Standalone Component vor. Dieses Verhalten lässt sich mit dem Schalter –standalone false
  • Die Anweisung ng g interceptor generiert funktionale Interceptors.

Zusammenfassung

Mit Version 17 schreitet die Angular Renaissance voran. Die neue Syntax für den Control Flow vereinfacht den Aufbau von Templates. Dank Deferred Loading können weniger wichtige Seitenbereiche zu einem späteren Zeitpunkt nachgeladen werden. Damit lässt sich der initiale Page-Load beschleunigen. Durch den Einsatz von esbuild laufen die Anweisungen ng build und ng serve merkbar schneller. Außerdem unterstützt das CLI nun direkt SSR und Prerendering.

 

ZUM NEWSLETTER

Regelmäßig News zur Konferenz und der .NET-Community

 

Links & Literatur

[1] https://github.com/manfredsteyer/hero-shop.git

[2] https://esbuild.github.io

[3] https://vitejs.dev

[4] https://developer.chrome.com/docs/web-platform/view-transitions/

[5] https://riegler.fr/blog/2023-10-04-animations-

Ihr aktueller Zugang zur .NET- und Microsoft-Welt.
Der BASTA! Newsletter:

Behind the Tracks

.NET Framework & C#
Visual Studio, .NET, Git, C# & mehr

Agile & DevOps
Agile Methoden, wie Scrum oder Kanban und Tools wie Visual Studio, Azure DevOps usw.

Web Development
Alle Wege führen ins Web

Data Access & Storage
Alles rund um´s Thema Data

JavaScript
Leichtegewichtig entwickeln

UI Technology
Alles rund um UI- und UX-Aspekte

Microservices & APIs
Services, die sich über APIs via REST und JavaScript nutzen lassen

Security
Tools und Methoden für sicherere Applikationen

Cloud & Azure
Cloud-basierte & Native Apps